package solver

import (
	"errors"
	"fmt"
	"os"
)

type Cell struct {
	value          uint8
	candidateCount int
	candidates     [9]bool
}

type House struct {
	solvedCount int
	cells       [9]*Cell
}

type Strategy func() bool

type Grid struct {
	solvedCount   int
	cells         [9][9]Cell
	rows          [9]House
	cols          [9]House
	boxes         [9]House
	strategies    []Strategy
	strategyNames []string
}

func (c *Cell) set(value uint8) {
	c.value = value
	if value == 0 {
		c.candidateCount = 9
		c.candidates = [9]bool{true, true, true, true, true, true, true, true, true}
	} else {
		c.candidateCount = 1
		c.candidates = [9]bool{false, false, false, false, false, false, false, false, false}
		c.candidates[value-1] = true
	}
}

func (c *Cell) removeCandidate(value uint8) bool {
	if c.candidateCount == 1 || !c.candidates[value-1] {
		return false
	}
	c.candidateCount -= 1
	c.candidates[value-1] = false
	return true
}

func (g *Grid) CheckInput() error {
	if len(os.Args) == 1 {
		fmt.Println("You must provide a sudoku string.")
		return errors.New("input error")
	}

	if len(os.Args[1]) < 81 {
		fmt.Println("The sudoku string must be 81 characters long.")
		return errors.New("input error")
	}

	for _, value := range os.Args[1] {
		if value < '0' || value > '9' {
			fmt.Println("The sudokku string must only cantain 0 to 9 digits.")
			return errors.New("input error")
		}
	}

	return nil
}

func (g *Grid) Input(in string) {
	g.solvedCount = 0
	for rowNum := 0; rowNum < 9; rowNum++ {
		for colNum := 0; colNum < 9; colNum++ {
			pos := rowNum*9 + colNum
			value := in[pos] - '0'
			if value != 0 {
				g.solvedCount += 1
			}
			g.cells[rowNum][colNum].set(value)
		}
	}
}

func (g *Grid) Init() {
	g.initRows()
	g.initCols()
	g.initBoxes()
	g.prepareStrategies()
}

func (g *Grid) initRows() {
	for rowNum := 0; rowNum < 9; rowNum++ {
		g.rows[rowNum].solvedCount = 0
		for colNum := 0; colNum < 9; colNum++ {
			g.rows[rowNum].cells[colNum] = &g.cells[rowNum][colNum]
			if g.cells[rowNum][colNum].value != 0 {
				g.rows[rowNum].solvedCount += 1
			}
		}
	}
}

func (g *Grid) initCols() {
	for colNum := 0; colNum < 9; colNum++ {
		g.cols[colNum].solvedCount = 0
		for rowNum := 0; rowNum < 9; rowNum++ {
			g.cols[colNum].cells[rowNum] = &g.cells[rowNum][colNum]
			if g.cells[rowNum][colNum].value != 0 {
				g.cols[colNum].solvedCount += 1
			}
		}
	}
}

func calcBoxAndCellNum(rowNum int, colNum int) (boxNum int, cellNum int) {
	boxNum = rowNum/3*3 + colNum/3
	cellNum = rowNum%3*3 + colNum%3
	return
}

func calcRowAndColNum(boxNum int, cellNum int) (rowNum int, colNum int) {
	rowNum = boxNum/3*3 + cellNum/3
	colNum = boxNum%3*3 + cellNum%3
	return
}

func (g *Grid) initBoxes() {
	for boxNum := 0; boxNum < 9; boxNum++ {
		g.boxes[boxNum].solvedCount = 0
		for cellNum := 0; cellNum < 9; cellNum++ {
			rowNum, colNum := calcRowAndColNum(boxNum, cellNum)
			g.boxes[boxNum].cells[cellNum] = &g.cells[rowNum][colNum]
			if g.cells[rowNum][colNum].value != 0 {
				g.boxes[boxNum].solvedCount += 1
			}
		}
	}
}

func printBoldLine() {
	fmt.Println("++===+===+===++===+===+===++===+===+===++")
}

func printFineLine() {
	fmt.Println("++---+---+---++---+---+---++---+---+---++")
}

func (g *Grid) printLineByCandidates(rowNum int, lineNum int) {
	fmt.Print("||")
	candStartNum := lineNum * 3
	for colNum := 0; colNum < 9; colNum++ {
		if g.cells[rowNum][colNum].value != 0 {
			if lineNum == 1 {
				fmt.Printf("(%d)", g.cells[rowNum][colNum].value)
			} else {
				fmt.Print("   ")
			}
		} else {
			for i := candStartNum; i < candStartNum+3; i++ {
				if g.cells[rowNum][colNum].candidates[i] {
					fmt.Print(i + 1)
				} else {
					fmt.Print(" ")
				}
			}
		}
		if (colNum+1)%3 == 0 {
			fmt.Print("||")
		} else {
			fmt.Print("|")
		}
	}
	fmt.Println()
}

func (g *Grid) PrintGrid() {
	printBoldLine()
	for row := 0; row < 9; row++ {
		g.printLineByCandidates(row, 0)
		g.printLineByCandidates(row, 1)
		g.printLineByCandidates(row, 2)
		if (row+1)%3 == 0 {
			printBoldLine()
		} else {
			printFineLine()
		}
	}
}
